﻿using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Reflection;

namespace Fast.Extensions.DatabaseAccessor.EntityFrameworkCore
{
    /// <summary>
    /// 数据库上下文构建器
    /// </summary>
    [SkipScan]
    internal static class AppDbContextBuilder
    {
        /// <summary>
        /// 数据库实体对应类型
        /// </summary>
        private static readonly IEnumerable<Type> DatabaseEntityTypes;
        /// <summary>
        /// 创建数据库实体方法
        /// </summary>
        private static readonly MethodInfo ModelBuildEntityMethod;
        /// <summary>
        /// 构造函数
        /// </summary>
        static AppDbContextBuilder()
        {
            DatabaseEntityTypes = App.Types.Where(t => (typeof(IRootEntity).IsAssignableFrom(t) || typeof(IRootModelBuilder).IsAssignableFrom(t)) && t.IsPublic && t.IsClass && !t.IsAbstract && !t.IsInterface && !t.IsGenericType);
            if (DatabaseEntityTypes.Any())
            {
                DbContextTypeContainer = new ConcurrentDictionary<Type, DbContextTypeContainer>();

                // 获取模型构建器 Entity<T> 方法
                ModelBuildEntityMethod = typeof(ModelBuilder).GetMethods(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(m => m.Name == nameof(ModelBuilder.Entity) && m.GetParameters().Length == 0);
            }
        }

        internal static void ConfigureDbContextEntity(ModelBuilder modelBuilder, DbContext dbContext, Type dbContextLocatorType)
        {
            //获取上下文所需要的类型
            var dbContextTypeContainer = GetDbContextTypes(dbContextLocatorType, dbContext);

            var dbContextType = dbContext.GetType();

            foreach (var entityType in dbContextTypeContainer.EntityTypes)
            {
                //获取实体构造器
                var entityBuilder = CreateEntityTypeBuilder(entityType, modelBuilder, dbContextType, dbContextLocatorType);
                if (entityBuilder == null) return;
                //配置无键实体类型
                ConfigureKeylessEntityType(entityType, entityBuilder, dbContextTypeContainer.KeylessEntityTypes);
                //实体类型构造之前
                EntityTypeBuilderOnCreating(entityBuilder, dbContext, dbContextTypeContainer.EntityTypeBuilderFilterInstances);
                //配置数据库实体构造器
                ConfigureEntityTypeBuilder(entityType, entityBuilder, dbContextTypeContainer);
                //配置数据库实体种子数据
                ConfigureEntitySeedData(entityType, entityBuilder, dbContext, dbContextTypeContainer);
                //实体类型构造之后
                EntityTypeBuilderOnCreated(entityBuilder, dbContext, dbContextTypeContainer.EntityTypeBuilderFilterInstances);
            }
        }
        /// <summary>
        /// 
        /// </summary>
        private static ConcurrentDictionary<Type, DbContextTypeContainer> DbContextTypeContainer;
        /// <summary>
        /// 获取数据库上下文中需要的类型
        /// </summary>
        /// <param name="dbContextLocator"></param>
        /// <param name="dbContext"></param>
        /// <returns></returns>
        private static DbContextTypeContainer GetDbContextTypes(Type dbContextLocator, DbContext dbContext)
        {
            return DbContextTypeContainer.GetOrAdd(dbContextLocator, GetDbContextType(dbContextLocator, dbContext));

            static DbContextTypeContainer GetDbContextType(Type dbContextLocator, DbContext dbContext)
            {
                var container = new DbContextTypeContainer();
                //获取当前数据库上下文中的实体类型
                var dbContextEntityTypes = DatabaseEntityTypes.Where(t => IsExistsDbContext(dbContextLocator, t));
                foreach (var dbContextEntityType in dbContextEntityTypes)
                {
                    if (typeof(IRootEntity).IsAssignableFrom(dbContextEntityType) && !dbContextEntityType.IsDefined(typeof(NotMappedAttribute), false))
                    {
                        //添加数据库实体
                        container.EntityTypes.Add(dbContextEntityType);
                        //添加数据库无键实体
                        if (typeof(IRootKeylessEntity).IsAssignableFrom(dbContextEntityType))
                        {
                            container.KeylessEntityTypes.Add(dbContextEntityType);
                        }
                    }
                    if (typeof(IRootModelBuilder).IsAssignableFrom(dbContextEntityType))
                    {
                        //添加有配置的实体类型
                        if (dbContextEntityType.HasImplementedTargetGeneric(typeof(IRootEntityTypeConfigure<>)))
                        {
                            container.EntityTypeConfigureTypes.Add(dbContextEntityType);
                        }
                        //添加有种子数据的实体类型
                        if (dbContextEntityType.HasImplementedTargetGeneric(typeof(IRootEntityTypeSeedDataConfigure<>)))
                        {
                            container.EntitySeedDataTypes.Add(dbContextEntityType);
                        }
                        //添加模型构建过滤器
                        if (dbContextEntityType.HasImplementedTargetGeneric(typeof(IEntityTypeBuilderFilter<>)))
                        {
                            if (typeof(DbContext).IsAssignableFrom(dbContextEntityType))
                            {
                                // 判断是否已经注册了上下文并且是否等于当前上下文
                                if (DatabaseHelper.DbContextTypeWithLocator.Values.Contains(dbContextEntityType) && dbContextEntityType == dbContext.GetType())
                                {
                                    container.EntityTypeBuilderFilterInstances.Add(dbContext as IEntityTypeBuilderFilter);
                                }
                            }
                        }
                    }
                }
                return container;
            }
        }
        /// <summary>
        /// 判断实体是否存在当前数据库上下文中
        /// </summary>
        /// <param name="dbContextLocator"></param>
        /// <param name="databaseEntityType"></param>
        /// <returns></returns>
        private static bool IsExistsDbContext(Type dbContextLocator, Type databaseEntityType)
        {
            // 获取所有父级类型
            var allParentTypes = databaseEntityType.GetAllParentTypes();
            //处理类似KeylessEntity的类型
            if (allParentTypes.Any(p => p.IsGenericType && p.GetGenericArguments().Contains(dbContextLocator))) return true;

            var interfaces = databaseEntityType.GetInterfaces().Where(i => typeof(IRootEntity).IsAssignableFrom(i) || typeof(IRootModelBuilder).IsAssignableFrom(i));

            return interfaces.Any(p => p.IsGenericType && p.GetGenericArguments().Contains(dbContextLocator));
        }
        /// <summary>
        /// 创建实体构造器
        /// </summary>
        /// <param name="type"></param>
        /// <param name="modelBuilder"></param>
        /// <param name="dbContextLocatorType"></param>
        /// <param name="dbContextType"></param>
        /// <returns></returns>
        private static EntityTypeBuilder CreateEntityTypeBuilder(Type type, ModelBuilder modelBuilder, Type dbContextType, Type dbContextLocatorType)
        {
            //反射生成实体构造器
            var entityTypeBuilder = ModelBuildEntityMethod.MakeGenericMethod(type).Invoke(modelBuilder, null) as EntityTypeBuilder;
            //配置实体表名
            ConfigureEntityTableName(type, entityTypeBuilder);

            if (dbContextLocatorType != typeof(MultiTenancyDbContextLocator) && !typeof(IRootMultiTenancy).IsAssignableFrom(dbContextType)
                && type.GetProperty(nameof(TenantEntity.TenantId)) != null)
            {
                entityTypeBuilder.Ignore(nameof(TenantEntity.TenantId));
            }

            return entityTypeBuilder;
        }
        /// <summary>
        /// 配置实体表名
        /// </summary>
        /// <param name="type"></param>
        /// <param name="entityTypeBuilder"></param>
        private static void ConfigureEntityTableName(Type type, EntityTypeBuilder entityTypeBuilder)
        {
            var tableAttribute = type.IsDefined(typeof(TableAttribute), true) ? type.GetCustomAttribute<TableAttribute>(true) : default;

            var tableName = tableAttribute?.Name ?? type.Name;

            entityTypeBuilder.ToTable(tableName);
        }
        /// <summary>
        /// 配置无键实体类型
        /// </summary>
        /// <param name="entityType"></param>
        /// <param name="entityTypeBuilder"></param>
        /// <param name="keylessEntityTypes"></param>
        private static void ConfigureKeylessEntityType(Type entityType, EntityTypeBuilder entityTypeBuilder, List<Type> keylessEntityTypes)
        {
            if (!keylessEntityTypes.Contains(entityType)) return;

            //配置视图、存储过程、函数无键实体
            entityTypeBuilder.HasNoKey();
            entityTypeBuilder.ToView((Activator.CreateInstance(entityType) as IRootKeylessEntity).GetName());
        }
        /// <summary>
        /// 实体类型构造之前 
        /// </summary>
        /// <param name="entityTypeBuilder"></param>
        /// <param name="dbContext"></param>
        /// <param name="entityTypeBuilderFilters"></param>
        private static void EntityTypeBuilderOnCreating(EntityTypeBuilder entityTypeBuilder, DbContext dbContext, List<IEntityTypeBuilderFilter> entityTypeBuilderFilters)
        {
            if (entityTypeBuilderFilters.Count == 0) return;
            // 创建过滤器
            foreach (var filterInstance in entityTypeBuilderFilters)
            {
                // 执行构建之后
                filterInstance.OnCreating(entityTypeBuilder, dbContext);
            }
        }
        /// <summary>
        /// 配置数据库实体构造器
        /// </summary>
        /// <param name="entityType"></param>
        /// <param name="entityTypeBuilder"></param>
        private static void ConfigureEntityTypeBuilder(Type entityType, EntityTypeBuilder entityTypeBuilder, DbContextTypeContainer dbContextTypeContainer)
        {
            var entityTypeConfigureTypes = dbContextTypeContainer.EntityTypeConfigureTypes
                .Where(t => t.GetInterfaces().Any(i => i.HasImplementedTargetGeneric(typeof(IRootEntityTypeConfigure<>)) && i.GetGenericArguments().Contains(entityType)));
            if (!entityTypeConfigureTypes.Any()) return;

            foreach (var entityTypeConfigureType in entityTypeConfigureTypes)
            {
                var configureMethod = entityTypeConfigureType.GetMethods(BindingFlags.Public | BindingFlags.Instance)
                    .Where(m => m.Name == "Configure" && m.GetParameters().Length > 0
                                                      && m.GetParameters().First().ParameterType == typeof(EntityTypeBuilder<>).MakeGenericType(entityType))
                    .FirstOrDefault();

                var instance = Activator.CreateInstance(entityTypeConfigureType);

                configureMethod?.Invoke(instance, new[] { entityTypeBuilder });
            }
        }
        /// <summary>
        /// 实体类型构造之后
        /// </summary>
        /// <param name="entityTypeBuilder"></param>
        /// <param name="dbContext"></param>
        /// <param name="entityTypeBuilderFilters"></param>
        private static void EntityTypeBuilderOnCreated(EntityTypeBuilder entityTypeBuilder, DbContext dbContext, List<IEntityTypeBuilderFilter> entityTypeBuilderFilters)
        {
            if (entityTypeBuilderFilters.Count == 0) return;
            // 创建过滤器
            foreach (var filterInstance in entityTypeBuilderFilters)
            {
                // 执行构建之后
                filterInstance.OnCreated(entityTypeBuilder, dbContext);
            }
        }
        /// <summary>
        /// 配置数据库实体种子数据
        /// </summary>
        /// <param name="entityType"></param>
        /// <param name="entityTypeBuilder"></param>
        /// <param name="dbContext"></param>
        /// <param name="dbContextTypeContainer"></param>
        private static void ConfigureEntitySeedData(Type entityType, EntityTypeBuilder entityTypeBuilder, DbContext dbContext, DbContextTypeContainer dbContextTypeContainer)
        {
            var entitySeedDataTypes = dbContextTypeContainer.EntitySeedDataTypes
                .Where(t => t.GetInterfaces().Any(i => i.HasImplementedTargetGeneric(typeof(IRootEntityTypeSeedDataConfigure<>)) && i.GetGenericArguments().Contains(entityType)));
            if (!entitySeedDataTypes.Any()) return;

            var data = new List<object>();
            foreach (var entitySeedDataType in entitySeedDataTypes)
            {
                var hasDataMethod = entitySeedDataType.GetMethods(BindingFlags.Public | BindingFlags.Instance)
                    .Where(m => m.Name == "HasData" && m.GetParameters().Length > 0
                                                      && m.GetParameters().First().ParameterType == typeof(DbContext))
                    .FirstOrDefault();

                var instance = Activator.CreateInstance(entitySeedDataType);
                var seedData = ((IList)hasDataMethod?.Invoke(instance, new[] { dbContext }))?.Cast<object>();
                if (seedData == null) continue;

                data.AddRange(seedData);
            }
            entityTypeBuilder.HasData(data.ToArray());
        }
    }
}
